package txpool

import (
	"osiris/config"
	"osiris/core/state"
	"osiris/dto"
	"osiris/logger"
	"sync"

	"github.com/jinzhu/copier"
)

const (
	bufSize = 16
)

var (

	// localTxPool 本地交易池
	localTxPool *LocalTxPool

	// remoteTxPool 远端交易池
	remoteTxPool *BaseTxPool

	// confirmedPledgeTxs 已上链确认的本地pledge交易
	confirmedPledgeTxs map[string][]dto.TxData

	// confirmedPledgeTxsMutex
	confirmedPledgeTxsMutex *sync.RWMutex

	// txDtoHashBuf TxDto.Hash缓存池
	txDtoHashBuf []string

	// bufMutex
	bufMutex *sync.RWMutex
)

func init() {
	localTxPool = &LocalTxPool{}
	remoteTxPool = &BaseTxPool{}
	confirmedPledgeTxs = make(map[string][]dto.TxData, 0)
	txDtoHashBuf = make([]string, 0)
	bufMutex = new(sync.RWMutex)
	confirmedPledgeTxsMutex = new(sync.RWMutex)
}

// InitTxPools 初始化所有交易池，涉及到数据库操作，应排在数据库初始化后面
func InitTxPools() {
	localTxPool.Init(config.LocalQueuedSize, config.LocalQueuedSize)
	remoteTxPool.Init(config.RemoteQueuedSize, config.RemotePendingSize)
}

// IsTxInPools 判断交易在不在交易池
//
//	@param fromAddrStr
//	@param txHash
//	@return bool
func IsTxInPools(fromAddrStr string, txHash string) bool {
	if localTxPool.IsTxInPool(fromAddrStr, txHash) {
		return true
	}

	if remoteTxPool.IsTxInPool(fromAddrStr, txHash) {
		return true
	}

	return false
}

func CloseTxPools() {
	localTxPool.Close()
	remoteTxPool.Close()
}

func AddTx(txData *dto.TxData) bool {
	if localTxPool.isLocalAddr(txData.FromAddr) {
		return AddLocalTx(txData)
	}

	return remoteTxPool.AddTx(txData)
}

func AddLocalTx(txData *dto.TxData) bool {
	return localTxPool.AddTx(txData)
}

func AddLocalAddr(addr []byte) {
	localTxPool.AddLocalAddr(addr)
}

func AddRecentTxDtoHash(hash string) {
	bufMutex.Lock()
	defer bufMutex.Unlock()

	if len(txDtoHashBuf) < bufSize {
		txDtoHashBuf = append(txDtoHashBuf, hash)
		return
	}

	txDtoHashBuf = txDtoHashBuf[1:]
	txDtoHashBuf = append(txDtoHashBuf, hash)
}

func IsHashInBuf(hash string) bool {
	bufMutex.RLock()
	defer bufMutex.RUnlock()

	for _, tempHash := range txDtoHashBuf {
		if tempHash == hash {
			return true
		}
	}

	return false
}

func TruncateTxPools() {
	localTxPool.Truncate()
	remoteTxPool.Truncate()
}

func addconfirmedPledgeTx(txData dto.TxData) {
	confirmedPledgeTxsMutex.Lock()
	defer confirmedPledgeTxsMutex.Unlock()

	txs, exist := confirmedPledgeTxs[txData.FromAddr]
	if !exist {
		confirmedPledgeTxs[txData.FromAddr] = []dto.TxData{txData}
	} else {
		confirmedPledgeTxs[txData.FromAddr] = append(txs, txData)
	}
}

func FlushConfirmedPledgeTxs() map[string][]dto.TxData {
	confirmedPledgeTxsMutex.Lock()
	defer confirmedPledgeTxsMutex.Unlock()

	txs := make(map[string][]dto.TxData)
	if len(confirmedPledgeTxs) == 0 {
		return txs
	}
	copier.CopyWithOption(&txs, confirmedPledgeTxs, copier.Option{IgnoreEmpty: true, DeepCopy: true})
	confirmedPledgeTxs = make(map[string][]dto.TxData, 0)
	
	return txs
}

func ResetLocalBF() error {
	return localTxPool.ResetLocalBF()
}

func GetAcountNonceInPool(chainID int, addrStr string) int64 {
	//TODO chainID
	nonce1 := localTxPool.getAcountNonceInPool(chainID, addrStr)
	if nonce1 != state.InvalidNonce {
		return nonce1
	}

	return remoteTxPool.getAcountNonceInPool(chainID, addrStr)
}

func Pack() []dto.TxData {
	//TODO chainID
	localPackTxs := localTxPool.Pack()
	remotePackTxs := remoteTxPool.Pack()
	go func() {
		for _, localTxs := range localPackTxs {
			logger.Info(map[string]interface{}{"[txpool] [Pack] local": localTxs.String()})
		}
		for _, remoteTxs := range remotePackTxs {
			logger.Info(map[string]interface{}{"[txpool] [Pack] remote": remoteTxs.String()})
		}
	}()
	return append(localPackTxs, remotePackTxs...)
}

func OnTxsConfirmed(txs []dto.TxData) {
	go localTxPool.OnTxsConfirmed(txs)
	go remoteTxPool.OnTxsConfirmed(txs)
}

func AddLocalSubscribe(subCh chan dto.TxData) {
	//TODO chainID
	localTxPool.AddConfirmSubscribe(subCh)
}

func AddRemoteSubscribe(subCh chan dto.TxData) {
	//TODO chainID
	remoteTxPool.AddConfirmSubscribe(subCh)
}

func GetLocalQueued() map[string]*TxDescendArray {
	return localTxPool.queued
}

func GetLocalPending() map[string][]*dto.TxData {
	return localTxPool.pending
}

func GetRemoteQueued() map[string]*TxDescendArray {
	return remoteTxPool.queued
}

func GetRemotePending() map[string][]*dto.TxData {
	return remoteTxPool.pending
}
